package com.atguigu.gulimall.product.web;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName IndexController
 * @Description TODO
 * @Detail detail
 * @Author MyPC
 * @Date 2020/7/6
 * @Version 1.0
 */
@Controller
public class IndexController {

    @Autowired
    CategoryService categoryService;

    @Autowired
    RedissonClient redisson;

    @Autowired
    StringRedisTemplate redisTemplate;

    /**
     * 访问首页
     * @param model
     * @return
     */
    @GetMapping({"/","/index.html"})
    public String indexPage(Model model){
        //获取一级分类数据
        List<CategoryEntity> categoryEntities=categoryService.getLevel1Categorys();
        //设置视图数据
        model.addAttribute("categorys",categoryEntities);
        //若方法返回值是String类型，则默认会跳转到 /templates/ + 名称.html页面
        return "index";
    }


    /**
     * TODO 使用spring-boot-starter-data-redis依赖时，其使用操作Redis的底层客户端是lettuce，它会配合netty进行网络通信，并发量会更高，但是会出现堆外内存溢出OutOfDirectMemory
     * 解决堆外溢出的办法：
     *      1、自己重写lettuce的源码，升级客户端
     *      2、更换为Jedis底层客户端，不过并发量没lettuce高
     *  lettuce、Jedis与RedisTemplate的区别：
     *      lettuce、Jedis都是操作Redis的底层客户端，而Spring针对lettuce、Jedis封装成了RedisTemplate，简化了操作，我们也是可以直接使用底层客户端来操作redis的
     * 从数据库查询三级分类数据
     *
     * @return
     */
    @ResponseBody  //响应Json数据
    @GetMapping("/index/catalog.json")
    public Map<Long, List<Catelog2Vo>>getCategorysJsonFromDB(){
        //获取分类数据，并封装成map集合，map的key就是一级分类的id
        Map<Long, List<Catelog2Vo>> categorysJson=categoryService.getCategorysJson();
        return categorysJson;
    }

    /**
     * 测试分布式锁
     *      分布式锁两大优点
     *          1、线程加锁成功之后，因为程序异常导致锁为正常关闭，那么锁会自动过期。因为Redisson中默认锁的存活时间是30秒
     *          2、业务未处理完成时，Redisson会自动帮我们给锁续期，，直到业务处理完毕。每次续期后锁的过期时间默认还是30秒
     * @return
     */
    @ResponseBody
    @GetMapping("/hello")
    public String hello(){
        //Redisson的分布式锁，它实现了juc包下的Lock接口
        RLock lock = redisson.getLock("hello-lock");
        //加锁，其他线程会阻塞式等待，一释放锁就去竞争锁，无需像自旋那样写循环代码
        //加锁之后，redis会存一份，键是唯一标识加上线程id
        //此锁占成功之后，就会启动定时任务，自动续期，续期时间是默认的释放锁的时间30秒的1/3，即10秒就会续一次时间，不用担心业务处理时间长短
        lock.lock();
        //此锁可以设置锁释放时间，超时时间就是我们设置的时间，并且不会自动续期，如果锁释放后业务还未处理完成，那么业务处理完之后，想要释放锁就会出现异常
        //最佳实战：给锁设置释放时间，省掉了整个续期操作，将锁释放时间可以调大，需要结合业处理时间，如果在释放时间之前业务都没有完成，说明业务出现问题了
        lock.lock(10L, TimeUnit.SECONDS);
        //处理业务
        try{
            System.out.println("加锁成功，处理业务。。。"+Thread.currentThread().getId());
            //睡10秒
            Thread.sleep(30000L);
        }catch (Exception e){

        } finally {
            //解锁,不管有没有异常都要进行解锁
            //解锁之后，redis会删除锁
            lock.unlock();
            System.out.println("解锁成功"+Thread.currentThread().getId());
        }
        return "hello";
    }

    /**
     * 写操作时，不允许读操作，写锁是一个排他锁（互斥锁）
     * 写锁没释放，读锁就必须等待，有了写锁，就可以保证读取的数据是最新的
     * @return
     */
    @GetMapping("/write")
    @ResponseBody
    public String writeLock(){
        String string = UUID.randomUUID().toString();
        //读写锁的名称相同，就代表同一把锁
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("wr-lock");
        //写锁
        RLock lock = readWriteLock.writeLock();
        try {
            //获取锁
            lock.lock();
            System.out.println("写锁加锁成功。。"+Thread.currentThread().getId());
            redisTemplate.opsForValue().set("writevalue",string);
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //不管有没有异常，都要释放锁
            lock.unlock();
            System.out.println("写锁释放成功。。"+Thread.currentThread().getId());
        }
        return string;
    }

    /**
     * 读速度是很快的，读锁是一个共享锁
     * @return
     */
    @GetMapping("/read")
    @ResponseBody
    public String readLock(){
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("wr-lock");
        //读锁
        RLock lock = readWriteLock.readLock();
        String read=null;
        lock.lock();
        try{
            System.out.println("读锁加锁成功。。"+Thread.currentThread().getId());
            read= redisTemplate.opsForValue().get("writevalue");
            Thread.sleep(10000L);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println("读锁释放成功。。"+Thread.currentThread().getId());
        }
        return read;
    }

    /**
     * 信号量测试
     * 模拟停车
     * @return
     */
    @GetMapping("/park")
    @ResponseBody
    public String park() throws InterruptedException {
        //名称相同，代表是相同的信号量
        RSemaphore semaphore = redisson.getSemaphore("count");
        //阻塞式,每执行一次，redis的信号量就会减1，如果没有信号量，则一直阻塞在这里
        //semaphore.acquire();
        //非阻塞式,如果没有信号量，则直接执行下面的逻辑
        boolean b = semaphore.tryAcquire();
        if(b){
            return "ok";
        }else {
            return "error =>";
        }

    }

    /**
     * 信号量测试
     * 模拟开车离开车位
     * @return
     */
    @GetMapping("/go")
    @ResponseBody
    public String go(){
        RSemaphore semaphore = redisson.getSemaphore("count");
        //每执行一次，redis的信号量就会加1
        semaphore.release();
        return "ok";
    }

    /**
     * 闭锁
     *  模拟锁门
     *      5个班的人都走了才可以锁门
     * @return
     */
    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {
        //名称相同则代表同一个闭锁
        RCountDownLatch lockDoor = redisson.getCountDownLatch("lockDoor");
        lockDoor.trySetCount(5L);
        //阻塞等待，当5个都被消耗完才执行下面的逻辑
        lockDoor.await();
        return "放学了";
    }
    /**
     * 闭锁
     *  模拟锁门
     *      5个班的人都走了才可以锁门
     * @return
     */
    @GetMapping("/gogo/{classId}")
    @ResponseBody
    public String gogo(@PathVariable("classId") Long classId){
        RCountDownLatch lockDoor = redisson.getCountDownLatch("lockDoor");
        //消耗，每次技术减1
        lockDoor.countDown();
        return classId+"班全走了";
    }
}
